// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'package:expect/async_helper.dart';
import 'package:expect/expect.dart';
import 'package:compiler/src/commandline_options.dart';
import 'package:compiler/src/common/elements.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/constants/values.dart';
import 'package:compiler/src/elements/entities.dart';
import 'package:compiler/src/elements/types.dart';
import 'package:compiler/src/environment.dart';
import 'package:compiler/src/ir/constants.dart';
import 'package:compiler/src/ir/visitors.dart';
import 'package:compiler/src/kernel/kernel_strategy.dart';
import 'package:compiler/src/kernel/element_map.dart';
import 'package:front_end/src/api_unstable/dart2js.dart' as ir;
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/type_environment.dart' as ir;
import 'package:compiler/src/util/memory_compiler.dart';

const emptyEnv = <String, String>{};

class TestData {
  final String name;

  /// Declarations needed for the [constants].
  final String declarations;

  /// Tested constants.
  final List<ConstantData> constants;

  const TestData(this.name, this.declarations, this.constants);
}

class ConstantData {
  /// Source code for the constant expression.
  final String code;

  /// Constant value as structured text for the empty environment or a map from
  /// environment to either the expected constant value as structured text or
  /// a [ConstantResult].
  final expectedResults;

  /// A [String] containing the code name for the error message expected as the
  /// result of evaluating the constant under the empty environment.
  final String? expectedError;

  const ConstantData(this.code, this.expectedResults, {this.expectedError});
}

const List<TestData> DATA = [
  TestData('simple', '', [
    ConstantData('null', 'NullConstant'),
    ConstantData('false', 'BoolConstant(false)'),
    ConstantData('true', 'BoolConstant(true)'),
    ConstantData('0', 'IntConstant(0)'),
    ConstantData('0.0', 'IntConstant(0)'),
    ConstantData('"foo"', 'StringConstant("foo")'),
    ConstantData('1 + 2', 'IntConstant(3)'),
    ConstantData('-(1)', 'IntConstant(-1)'),
    ConstantData('1 == 2', 'BoolConstant(false)'),
    ConstantData('1 != 2', 'BoolConstant(true)'),
    ConstantData('1 / 0', 'DoubleConstant(Infinity)'),
    ConstantData('0 / 0', 'DoubleConstant(NaN)'),
    ConstantData('1 << 0', 'IntConstant(1)'),
    ConstantData('1 >> 0', 'IntConstant(1)'),
    ConstantData('"foo".length', 'IntConstant(3)'),
    ConstantData('identical(0, 1)', 'BoolConstant(false)'),
    ConstantData('"a" "b"', 'StringConstant("ab")'),
    ConstantData(r'"${null}"', 'StringConstant("null")'),
    ConstantData('identical', 'FunctionConstant(identical)'),
    ConstantData('true ? 0 : 1', 'IntConstant(0)'),
    ConstantData(
      'deprecated',
      'ConstructedConstant(Deprecated(message=StringConstant("next release")))',
    ),
    ConstantData('const [] == null', 'BoolConstant(false)'),
    ConstantData('deprecated == null', 'BoolConstant(false)'),
    ConstantData('deprecated != null', 'BoolConstant(true)'),
    ConstantData('null == deprecated', 'BoolConstant(false)'),
    ConstantData('null != deprecated', 'BoolConstant(true)'),
    ConstantData('true == deprecated', 'BoolConstant(false)'),
    ConstantData('true != deprecated', 'BoolConstant(true)'),
    ConstantData('0 == deprecated', 'BoolConstant(false)'),
    ConstantData('0 != deprecated', 'BoolConstant(true)'),
    ConstantData('0.5 == deprecated', 'BoolConstant(false)'),
    ConstantData('0.5 != deprecated', 'BoolConstant(true)'),
    ConstantData('"" == deprecated', 'BoolConstant(false)'),
    ConstantData('"" != deprecated', 'BoolConstant(true)'),
    ConstantData('Object', 'TypeConstant(Object)'),
    ConstantData('null ?? 0', 'IntConstant(0)'),
    ConstantData(
      'const <int, int>{0: 1, 0: 2}',
      'NonConstant',
      expectedError: 'ConstEvalDuplicateKey',
    ),
    ConstantData(
      'const bool.fromEnvironment("foo", defaultValue: false)',
      <Map<String, String>, String>{
        emptyEnv: 'BoolConstant(false)',
        const {'foo': 'true'}: 'BoolConstant(true)',
      },
    ),
    ConstantData(
      'const int.fromEnvironment("foo", defaultValue: 42)',
      <Map<String, String>, String>{
        emptyEnv: 'IntConstant(42)',
        const {'foo': '87'}: 'IntConstant(87)',
      },
    ),
    ConstantData(
      'const String.fromEnvironment("foo", defaultValue: "bar")',
      <Map<String, String>, String>{
        emptyEnv: 'StringConstant("bar")',
        const {'foo': 'foo'}: 'StringConstant("foo")',
      },
    ),
    ConstantData(
      'const [0, 1]',
      'ListConstant(<int>[IntConstant(0), IntConstant(1)])',
    ),
    ConstantData(
      'const <int>[0, 1]',
      'ListConstant(<int>[IntConstant(0), IntConstant(1)])',
    ),
    ConstantData(
      'const {0, 1}',
      'SetConstant(<int>{IntConstant(0), IntConstant(1)})',
    ),
    ConstantData(
      'const <int>{0, 1}',
      'SetConstant(<int>{IntConstant(0), IntConstant(1)})',
    ),
    ConstantData(
      'const {0: 1, 2: 3}',
      'MapConstant(<int, int>{IntConstant(0): IntConstant(1), '
          'IntConstant(2): IntConstant(3)})',
    ),
    ConstantData(
      'const <int, int>{0: 1, 2: 3}',
      'MapConstant(<int, int>{IntConstant(0): IntConstant(1), '
          'IntConstant(2): IntConstant(3)})',
    ),
  ]),
  TestData(
    'env',
    '''
const a = bool.fromEnvironment("foo", defaultValue: true);
const b = int.fromEnvironment("bar", defaultValue: 42);

class A {
  const A();
}
class B {
  final field1;
  const B(this.field1);
}
class C extends B {
  final field2;
  const C({field1: 42, this.field2: false}) : super(field1);
  const C.named([field = false]) : this(field1: field, field2: field);
}
class D extends C {
  final field3 = 99;
  const D(a, b) : super(field2: a, field1: b);
}
''',
    [
      ConstantData('const Object()', 'ConstructedConstant(Object())'),
      ConstantData('const A()', 'ConstructedConstant(A())'),
      ConstantData(
        'const B(0)',
        'ConstructedConstant(B(field1=IntConstant(0)))',
      ),
      ConstantData(
        'const B(A())',
        'ConstructedConstant(B(field1=ConstructedConstant(A())))',
      ),
      ConstantData(
        'const C()',
        'ConstructedConstant(C(field1=IntConstant(42),'
            'field2=BoolConstant(false)))',
      ),
      ConstantData(
        'const C(field1: 87)',
        'ConstructedConstant(C(field1=IntConstant(87),'
            'field2=BoolConstant(false)))',
      ),
      ConstantData(
        'const C(field2: true)',
        'ConstructedConstant(C(field1=IntConstant(42),'
            'field2=BoolConstant(true)))',
      ),
      ConstantData(
        'const C.named()',
        'ConstructedConstant(C(field1=BoolConstant(false),'
            'field2=BoolConstant(false)))',
      ),
      ConstantData(
        'const C.named(87)',
        'ConstructedConstant(C(field1=IntConstant(87),'
            'field2=IntConstant(87)))',
      ),
      ConstantData(
        'const C(field1: a, field2: b)',
        <Map<String, String>, String>{
          emptyEnv:
              'ConstructedConstant(C(field1=BoolConstant(true),'
              'field2=IntConstant(42)))',
          const {'foo': 'false', 'bar': '87'}:
              'ConstructedConstant(C(field1=BoolConstant(false),'
              'field2=IntConstant(87)))',
        },
      ),
      ConstantData(
        'const D(42, 87)',
        'ConstructedConstant(D(field1=IntConstant(87),'
            'field2=IntConstant(42),'
            'field3=IntConstant(99)))',
      ),
    ],
  ),
  TestData(
    'redirect',
    '''
class A<T> implements B<Null> {
  final field1;
  const A({this.field1:42});
}
class B<S> implements C<Null> {
  const factory B({field1}) = A<B<S>>;
  const factory B.named() = A<S>;
}
class C<U> {
  const factory C({field1}) = A<B<double>>;
}
''',
    [
      ConstantData(
        'const A()',
        'ConstructedConstant(A<dynamic>(field1=IntConstant(42)))',
      ),
      ConstantData(
        'const A<int>(field1: 87)',
        'ConstructedConstant(A<int>(field1=IntConstant(87)))',
      ),
      ConstantData(
        'const B()',
        'ConstructedConstant(A<B<dynamic>>(field1=IntConstant(42)))',
      ),
      ConstantData(
        'const B<int>()',
        'ConstructedConstant(A<B<int>>(field1=IntConstant(42)))',
      ),
      ConstantData(
        'const B<int>(field1: 87)',
        'ConstructedConstant(A<B<int>>(field1=IntConstant(87)))',
      ),
      ConstantData(
        'const C<int>(field1: 87)',
        'ConstructedConstant(A<B<double>>(field1=IntConstant(87)))',
      ),
      ConstantData(
        'const B<int>.named()',
        'ConstructedConstant(A<int>(field1=IntConstant(42)))',
      ),
    ],
  ),
  TestData(
    'env2',
    '''
const c = int.fromEnvironment("foo", defaultValue: 5);
const d = int.fromEnvironment("bar", defaultValue: 10);

class A {
  final field;
  const A(a, b) : field = a + b;
}

class B extends A {
  const B(a) : super(a, a * 2);
}
''',
    [
      ConstantData('const A(c, d)', <Map<String, String>, String>{
        emptyEnv: 'ConstructedConstant(A(field=IntConstant(15)))',
        const {'foo': '7', 'bar': '11'}:
            'ConstructedConstant(A(field=IntConstant(18)))',
      }),
      ConstantData('const B(d)', <Map<String, String>, String>{
        emptyEnv: 'ConstructedConstant(B(field=IntConstant(30)))',
        const {'bar': '42'}: 'ConstructedConstant(B(field=IntConstant(126)))',
      }),
    ],
  ),
  TestData(
    'construct',
    '''
 class A {
   final x;
   final y;
   final z;
   final t;
   final u = 42;
   const A(this.z, tt) : y = 499, t = tt, x = 3;
   const A.named(z, this.t) : y = 400 + z, this.z = z, x = 3;
   const A.named2(t, z, y, x) : x = t, y = z, z = y, t = x;
 }
 ''',
    [
      ConstantData(
        'const A.named(99, 100)',
        'ConstructedConstant(A('
            't=IntConstant(100),'
            'u=IntConstant(42),'
            'x=IntConstant(3),'
            'y=IntConstant(499),'
            'z=IntConstant(99)))',
      ),
      ConstantData(
        'const A(99, 100)',
        'ConstructedConstant(A('
            't=IntConstant(100),'
            'u=IntConstant(42),'
            'x=IntConstant(3),'
            'y=IntConstant(499),'
            'z=IntConstant(99)))',
      ),
    ],
  ),
  TestData(
    'errors',
    r'''
 const dynamic null_ = bool.fromEnvironment('x') ? null : null;
 const dynamic zero = bool.fromEnvironment('x') ? null : 0;
 const dynamic minus_one = bool.fromEnvironment('x') ? null : -1;
 const dynamic false_ = bool.fromEnvironment('x') ? null : false;
 const dynamic integer = int.fromEnvironment("foo", defaultValue: 5);
 const dynamic string = String.fromEnvironment("bar", defaultValue: "baz");
 const dynamic boolean = bool.fromEnvironment("baz", defaultValue: false);
 const dynamic not_string =
    bool.fromEnvironment("not_string", defaultValue: false) ? '' : 0;
 class Class1 {
    final field;
    const Class1() : field = not_string.length;
 }
 class Class2 implements Class3 {
    const Class2() : assert(false_);
    const Class2.redirect() : this();
 }
 class Class3 {
    const Class3() : assert(false_, "Message");
    const factory Class3.fact() = Class2;
 }
 class Class4 extends Class2 {
    const Class4();
 }
 class Class5 {
    const Class5(a) : assert(a > 0, "$a <= 0");
 }
 class Class6 extends Class5 {
    const Class6(a) : super(a - 1);
 }
 class Class7 {
    const Class7();

    bool operator ==(Object other) => true;
 }
 class Class8 {
    final field;
    const Class8(this.field);
 }
 class Class9 {
    final field = null_;
    const Class9();
 }
 class Class10 {
    final int field = string;
    const Class10();
 }
 ''',
    [
      ConstantData(
        r'"$integer $string $boolean"',
        'StringConstant("5 baz false")',
      ),
      ConstantData(
        'integer ? true : false',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        r'"${deprecated}"',
        'NonConstant',
        expectedError: 'ConstEvalInvalidStringInterpolationOperand',
      ),
      ConstantData(
        '0 + string',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        'string + 0',
        'NonConstant',
        expectedError: 'ConstEvalInvalidBinaryOperandType',
      ),
      ConstantData(
        'boolean + string',
        'NonConstant',
        expectedError: 'ConstEvalInvalidMethodInvocation',
      ),
      ConstantData(
        'boolean + false',
        'NonConstant',
        expectedError: 'ConstEvalInvalidMethodInvocation',
      ),
      ConstantData(
        '0 * string',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        '0 % string',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        '0 << string',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        '1 ~/ zero',
        'NonConstant',
        expectedError: 'ConstEvalZeroDivisor',
      ),
      ConstantData(
        '1 % zero',
        'NonConstant',
        expectedError: 'ConstEvalZeroDivisor',
      ),
      ConstantData(
        '1 << minus_one',
        'NonConstant',
        expectedError: 'ConstEvalNegativeShift',
      ),
      ConstantData(
        '1 >> minus_one',
        'NonConstant',
        expectedError: 'ConstEvalNegativeShift',
      ),
      ConstantData(
        'const bool.fromEnvironment(integer)',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        'const bool.fromEnvironment("baz", defaultValue: integer)',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        'const int.fromEnvironment(integer)',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        'const int.fromEnvironment("baz", defaultValue: string)',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        'const String.fromEnvironment(integer)',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        'const String.fromEnvironment("baz", defaultValue: integer)',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        'false || integer',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        'integer || true',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        'integer && true',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        '!integer',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        '!string',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        '-(string)',
        'NonConstant',
        expectedError: 'ConstEvalInvalidMethodInvocation',
      ),
      ConstantData(
        'not_string.length',
        'NonConstant',
        expectedError: 'ConstEvalInvalidPropertyGet',
      ),
      ConstantData(
        'const Class1()',
        'NonConstant',
        expectedError: 'ConstEvalInvalidPropertyGet',
      ),
      ConstantData(
        'const Class2()',
        'NonConstant',
        expectedError: 'ConstEvalFailedAssertion',
      ),
      ConstantData(
        'const Class2.redirect()',
        'NonConstant',
        expectedError: 'ConstEvalFailedAssertion',
      ),
      ConstantData(
        'const Class3()',
        'NonConstant',
        expectedError: 'ConstEvalFailedAssertionWithMessage',
      ),
      ConstantData(
        'const Class3.fact()',
        'NonConstant',
        expectedError: 'ConstEvalFailedAssertion',
      ),
      ConstantData(
        'const Class4()',
        'NonConstant',
        expectedError: 'ConstEvalFailedAssertion',
      ),
      ConstantData(
        'const Class5(0)',
        'NonConstant',
        expectedError: 'ConstEvalFailedAssertionWithMessage',
      ),
      ConstantData('const Class5(1)', 'ConstructedConstant(Class5())'),
      ConstantData(
        'const Class6(1)',
        'NonConstant',
        expectedError: 'ConstEvalFailedAssertionWithMessage',
      ),
      ConstantData('const Class6(2)', 'ConstructedConstant(Class6())'),
      ConstantData('const Class7()', 'ConstructedConstant(Class7())'),
      ConstantData(
        'const Class7() == const Class7()',
        'NonConstant',
        expectedError: 'ConstEvalEqualsOperandNotPrimitiveEquality',
      ),
      ConstantData(
        'const Class7() != const Class7()',
        'NonConstant',
        expectedError: 'ConstEvalEqualsOperandNotPrimitiveEquality',
      ),
      ConstantData(
        'const Class8(not_string.length)',
        'NonConstant',
        expectedError: 'ConstEvalInvalidPropertyGet',
      ),
      ConstantData(
        'const Class9()',
        'ConstructedConstant(Class9(field=NullConstant))',
      ),
      ConstantData(
        'const Class10()',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
    ],
  ),
  TestData(
    'assert',
    '''
    const true_ = bool.fromEnvironment('x') ? null : true;
    class A {
      const A() : assert(true);
    }
    class B {
      const B() : assert(true, "Message");
    }
    class C {
      final a;
      const C(this.a);
    }
    class D extends C {
      final b;
      const D(c) : b = c + 2, super(c + 1);
    }
  ''',
    [
      ConstantData(r'const A()', 'ConstructedConstant(A())'),
      ConstantData(r'const B()', 'ConstructedConstant(B())'),
      ConstantData(
        r'const D(0)',
        'ConstructedConstant(D(a=IntConstant(1),b=IntConstant(2)))',
      ),
    ],
  ),
  TestData(
    'instantiations',
    '''
T identity<T>(T t) => t;
class C<T> {
  final T defaultValue;
  final T Function(T t) identityFunction;

  const C(this.defaultValue, this.identityFunction);
}
  ''',
    [
      ConstantData('identity', 'FunctionConstant(identity)'),
      ConstantData(
        'const C<int>(0, identity)',
        'ConstructedConstant(C<int>(defaultValue=IntConstant(0),'
            'identityFunction=InstantiationConstant([int],'
            'FunctionConstant(identity))))',
      ),
      ConstantData(
        'const C<double>(0.5, identity)',
        'ConstructedConstant(C<double>(defaultValue=DoubleConstant(0.5),'
            'identityFunction=InstantiationConstant([double],'
            'FunctionConstant(identity))))',
      ),
    ],
  ),
  TestData(
    'generic class',
    '''
class C<T> {
  const C.generative();
  const C.redirect() : this.generative();
}
  ''',
    <ConstantData>[
      ConstantData(
        'const C<int>.generative()',
        'ConstructedConstant(C<int>())',
      ),
      ConstantData('const C<int>.redirect()', 'ConstructedConstant(C<int>())'),
    ],
  ),
  TestData(
    'instance',
    '''
const dynamic zero_ = bool.fromEnvironment("x") ? null : 0;
class Class9 {
  final field = zero_;
  const Class9();
}
''',
    <ConstantData>[
      ConstantData(
        'const Class9()',
        'ConstructedConstant(Class9(field=IntConstant(0)))',
      ),
    ],
  ),
  TestData(
    'type-variables',
    '''
class A {
  const A();
}

class C1<T> {
  final T a;
  const C1(dynamic t) : a = t; // adds implicit cast `as T`
}

T id<T>(T t) => t;

class C2<T> {
  final T Function(T) a;
  const C2(dynamic t) : a = id; // implicit partial instantiation
}
''',
    <ConstantData>[
      ConstantData(
        'const C1<A>(A())',
        'ConstructedConstant(C1<A>(a=ConstructedConstant(A())))',
      ),
      ConstantData(
        'const C2<A>(id)',
        'ConstructedConstant(C2<A>(a='
            'InstantiationConstant([A],FunctionConstant(id))))',
      ),
    ],
  ),
  TestData(
    'unused-arguments',
    '''
class A {
  const A();

  A operator -() => this;
}
class B implements A {
  const B();

  B operator -() => this;
}
class C implements A {
  const C();

  C operator -() => this;
}
class Class<T extends A> {
  const Class(T t);
  const Class.redirect(dynamic t) : this(t);
  const Class.method(T t) : this(-t);
}
class Subclass<T extends A> extends Class<T> {
  const Subclass(dynamic t) : super(t);
}
''',
    [
      ConstantData(
        'const Class<B>.redirect(C())',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData(
        'const Class<A>.method(A())',
        'NonConstant',
        expectedError:
            'The argument type \'A\' can\'t be '
            'assigned to the parameter type \'T\'.',
      ),
      ConstantData(
        'const Subclass<B>(C())',
        'NonConstant',
        expectedError: 'ConstEvalInvalidType',
      ),
      ConstantData('const Class<A>(A())', 'ConstructedConstant(Class<A>())'),
      ConstantData(
        'const Class<B>.redirect(B())',
        'ConstructedConstant(Class<B>())',
      ),
      ConstantData(
        'const Subclass<A>(A())',
        'ConstructedConstant(Subclass<A>())',
      ),
      ConstantData(
        'const Subclass<B>(B())',
        'ConstructedConstant(Subclass<B>())',
      ),
    ],
  ),
  TestData(
    'Nested Unevaluated',
    '''
class Foo {
  const Foo(
    int Function(String)? a1,
    int Function(String)? a2,
    int Function(String)? a3,
    int Function(String)? a4,
  ) : _foo = a1 ??
            a2 ??
            a3 ??
            a4 ??
            bar;
  final int Function(String) _foo;
}

int bar(String o) => int.parse(o);
 ''',
    [
      ConstantData(
        '''Foo(
    bool.fromEnvironment("baz") ? int.parse : null,
    bool.fromEnvironment("baz") ? int.parse : null,
    bool.fromEnvironment("baz") ? int.parse : null,
    bool.fromEnvironment("baz") ? int.parse : null,
  )''',
        <Map<String, String>, String>{
          emptyEnv: 'ConstructedConstant(Foo(_foo=FunctionConstant(bar)))',
          const {'baz': 'true'}:
              'ConstructedConstant(Foo(_foo=FunctionConstant(int.parse)))',
        },
      ),
      ConstantData(
        '''String.fromEnvironment(String.fromEnvironment(String.fromEnvironment("foo")))''',
        <Map<String, String>, String>{
          emptyEnv: 'StringConstant("")',
          const {'foo': 'bar', 'bar': 'baz'}: 'StringConstant("")',
          const {'foo': 'bar', 'bar': 'baz', 'baz': 'hello'}:
              'StringConstant("hello")',
          const {'foo': 'bar', 'bar': 'baz', 'baz': 'world'}:
              'StringConstant("world")',
        },
      ),
    ],
  ),
];

Future testData(TestData data) async {
  // Group tests by environment and then by expected error so that we can
  // distinguish which constant triggered which errors in the CFE. There
  // are too many constants to compile each individually, this test would
  // timeout.
  Map<Map<String, String>, Map<String?, List<(ConstantData, String)>>>
  constants = {};
  data.constants.forEach((ConstantData constantData) {
    final expectedResult = constantData.expectedResults;
    if (expectedResult is String) {
      ((constants[emptyEnv] ??= {})[constantData.expectedError] ??= []).add((
        constantData,
        expectedResult,
      ));
    } else if (expectedResult is Map<Map<String, String>, String>) {
      expectedResult.forEach((env, expectedString) {
        ((constants[env] ??= {})[constantData.expectedError] ??= []).add((
          constantData,
          expectedString,
        ));
      });
    }
  });

  for (final env in constants.keys) {
    final expectations = constants[env]!;
    for (final errorString in expectations.keys) {
      StringBuffer sb = StringBuffer();
      sb.writeln('${data.declarations}');
      final constantEntries = expectations[errorString]!;
      final envData = <(String, (ConstantData, String))>[];
      for (final constantEntry in constantEntries) {
        final name = 'c${envData.length}';
        final code = constantEntry.$1.code;
        envData.add((name, constantEntry));
        // Encode the constants as part of a from-environment conditional to
        // force CFE to create unevaluated constants.
        sb.writeln('const $name = bool.fromEnvironment("x") ? null : $code;');
      }
      sb.writeln('main() {');
      for (final (name, _) in envData) {
        sb.writeln('  print($name);');
      }
      sb.writeln('}');
      String source = sb.toString();
      print(
        "--source '${data.name}'-------------------------------------------",
      );
      print("Compiling with env: $env");
      print(source);
      await runEnvTest(env, source, envData, errorString);
    }
  }
}

Future<void> runEnvTest(
  Map<String, String> env,
  String source,
  List<(String, (ConstantData, String))> envData,
  String? expectedError,
) async {
  final diagnosticCollector = DiagnosticCollector();
  CompilationResult result = await runCompiler(
    memorySourceFiles: {'main.dart': source},
    options: [Flags.enableAsserts, Flags.testMode],
    environment: {...env},
    diagnosticHandler: diagnosticCollector,
  );
  Compiler compiler = result.compiler!;
  KernelFrontendStrategy frontEndStrategy = compiler.frontendStrategy;
  KernelToElementMap elementMap = frontEndStrategy.elementMap;
  DartTypes dartTypes = elementMap.types;
  ir.TypeEnvironment typeEnvironment = elementMap.typeEnvironment;
  KElementEnvironment elementEnvironment =
      compiler.frontendStrategy.elementEnvironment;
  ConstantValuefier constantValuefier = ConstantValuefier(elementMap);
  LibraryEntity library = elementEnvironment.mainLibrary!;
  for (final (name, (data, expectedText)) in envData) {
    final field = elementEnvironment.lookupLibraryMember(library, name)!;
    compiler.reporter.withCurrentElement(field, () {
      final node = elementMap.getMemberNode(field) as ir.Field;
      print('-- testing $field = ${data.code} --');
      Dart2jsConstantEvaluator evaluator = Dart2jsConstantEvaluator(
        elementMap.env.mainComponent,
        elementMap.typeEnvironment,
        (ir.LocatedMessage message, List<ir.LocatedMessage>? context) {
          // Constants should be fully evaluated by this point so there should be
          // no new messages.
          throw StateError('There should be no unevaluated errors in the AST.');
        },
        environment: Environment(env),
        supportReevaluationForTesting: true,
      );
      ir.Constant evaluatedConstant = evaluator.evaluate(
        ir.StaticTypeContext(node, typeEnvironment),
        node.initializer!,
      );

      ConstantValue? value =
          evaluatedConstant is! ir.UnevaluatedConstant
              ? constantValuefier.visitConstant(evaluatedConstant)
              : null;

      String valueText = value?.toStructuredText(dartTypes) ?? 'NonConstant';
      Expect.equals(
        expectedText,
        valueText,
        "Unexpected value '${valueText}' for field $field = "
        "`${data.code}` in env $env, "
        "expected '${expectedText}'.",
      );

      final errors = diagnosticCollector.contexts.map((e) => e.text).toList();
      if (expectedError != null) {
        if (node.initializer is ir.InvalidExpression) {
          Expect.isTrue(
            diagnosticCollector.errors.any(
              (e) => e.text.contains(expectedError),
            ),
            "Error mismatch for `$field = ${data.code}`:\n"
            "Expected to contain: ${expectedError},\n"
            "Found: ${diagnosticCollector.errors.map((e) => e.text)}.",
          );
        } else {
          // There should be 2 errors per constant in this test group, 1 for the
          // declaration and another for the print.
          Expect.equals(envData.length * 2, errors.length);
          Expect.isTrue(
            errors.every((e) => e == expectedError),
            "Error mismatch for `$field = ${data.code}`:\n"
            "Expected: ${expectedError},\n"
            "Found: ${errors}.",
          );
        }
      } else {
        Expect.isTrue(
          diagnosticCollector.contexts.isEmpty,
          "Unexpected errors for `$field = ${data.code}`:\n"
          "Found: ${errors}.",
        );
      }
    });
  }
}

const int totalShards = 2;

void testShard(List<String> args, int shard) {
  if (shard >= totalShards) throw ArgumentError('Shard number invalid: $shard');
  asyncTest(() async {
    int i = 0;
    for (TestData data in DATA) {
      i++;
      if (i % totalShards != shard) continue;
      if (args.isNotEmpty && !args.contains(data.name)) continue;
      await testData(data);
    }
  });
}
